iT邦幫忙

DAY 14
1

Reactjs 30 天邊做邊學系列系列 第 14

Reactjs Day 14 - 使用 Refs

  • 分享至 

  • xImage
  •  

關於 Refs
當你透過 render() 回傳你的 UI 結構之後,你可能想要從外部調用這個元件實例的方法。通常情況下為了取得一些元件或計算後資料你可能這樣做,但其實是不必要的,因為 React 通常會確保資料是最新的 props 且透過 render() 傳遞到子元件。不過的確有些情況還是會需要從外部調用方法。
想像下面這種狀況,當你想讓一個已存在的某元件的子元件 <input /> 在你清空欄位後馬上 focus<input />

var App = React.createClass({
  getInitialState: function() {
    return {userInput: ''};
  },
  handleKeyUp: function(e) {
    this.setState({userInput: this.getDOMNode().value});
    // this.setState({userInput: e.target.value}); # 官方範例使用 e.target.value 。
  },
  clearAndFocusInput: function() {
    this.setState({userInput: ''}); // Clear the input
    // 我們希望在這邊可以 focus <input />

  },
  render: function() {
    return (
      <div>
        <div onClick={this.clearAndFocusInput}>
          Click To Focus and Reset
        </div>
        <input
          ref="theInput"
          value={this.state.userInput}
          onKeyUp={this.handleKeyUp}
        />
      </div>
    );
  }
});
React.renderComponent(<App />, document.getElementById('example'));

請注意,在這個範例我們想要通知 <input /> 做些事情。這件事件沒辦法透過 props 辦到。我們想要讓 <input /> focus ,然而這邊遇到了一點問題,那就是 render() 回傳的不是子元素 <input /> 的元件,在這個時候它只是一子元件的結構描述。
記得當你從 render() 回傳一個結構,它並不包含替你產生子元素的物件實例。關於在內部的標簽(內部的元件),它們都只是結構描述。
不過你可能已經想到把 <input /> 先存在變數裡了,注意!!你不應該把『這些事情或物件』存起來,然後期待這可能有一天會用到。如下

// 錯誤範例: DO NOT DO THIS!
render: function() {
  var myInput = <input />;          // 我可能會呼叫這個物件的 Method
  this.rememberThisInput = myInput; // 在未來某個時間點我就可以直接呼叫他
  return (
    <div>
      <div>...</div>
      {myInput}
    </div>
  );
}

在這個錯誤範例中,<input /> 只是描述結構。這個描述是用來建立 <input /> 背後的實際物件。
如果我們把這段程式完成如下並試著觀察這段程式碼的運作,事實上當你呼叫 this.rememberThisInput 是會出 ERROR 的!

    <title>Hello React</title>
    <script src="http://fb.me/react-0.8.0.js"></script>
    <script src="http://fb.me/JSXTransformer-0.8.0.js"></script>
    <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
  
  
    <div id="example"></div>
    <div>
      原生 Javascript 範例示範 focus 所以我們可以透過 element.focus() 讓元素 focus。
      <div id='test-trigger'>Click!</div>
      <input type='text' id='test' />
    </div>
    <script>
    /* 原生 Javascript 範例示範 focus */
    var HandleClick = function (e) {
      var el = document.getElementById('test');
      el.focus();
    };
    var el = document.getElementById('test-trigger');
    if (el.addEventListener) {
      el.addEventListener('click', HandleClick, false);
    } else {
      el.attachEvent('onclick', HandleClick);
    }
    </script>

    <script type="text/jsx">
    /** @jsx React.DOM */
    var App = React.createClass({
      getInitialState: function () {
        return {userInput: ''}
      },
      handleKeyUp: function (e) {
        this.setState({userInput: this.getDOMNode().value});
      },
      clearAndFocusInput: function () {
        this.setState({userInput: ''});
        // console.log(this.rememberThisInput);
        // this.rememberThisInput.focus();
      },
      render: function () {
        var myInput = <input value={this.state.userInput} onKeyUp={this.handleKeyUp} />
        this.rememberThisInput = myInput;
        return (
          <div>
            <div onClick={this.clearAndFocusInput} >Click to Focus and Reset</div>
            {myInput}
          </div>
        );
      }

    });
    React.renderComponent(<App />, document.getElementById('example'));
    </script>
  

那我們怎麼通知 <input /> 背後的這個物件呢?

ref 屬性(attribute)
React 支援一個非常特別的屬性,你可以把它附加到任何在 render() 裡面的元件上(就是標簽 tag 上)。這個特殊的屬性可以讓你存取到對應的『背後的實際物件』,它保證可以在任何時間點存取到當下的物件。
下面是一個範例:
1 在 render() 裡將回傳任意的元素設定 ref 屬性(attribute)

<input ref='myInput' />

2 在程式碼(典型的範例是在處理事件或函式裡)中你就可以透過 this.refs 存取這個『背後的物件』。

this.refs.myInput

完整的範例

/** @jsx React.DOM */
var App = React.createClass({
  getInitialState: function () {
    return {userInput: ''};
  },
  handleKeyUp: function (e) {
    this.setState({userInput: this.getDOMNode().value});
    console.log(e); // 官方範例使用的 e.target.value 是錯誤的!

  },
  clearAndFocusInput: function (e) {
    this.setState({userInput: ''});
    this.refs.theInput.getDOMNode().focus();
  },
  render: function () {
    return (
      <div>
        <div onClick={this.clearAndFocusInput}>
          Click To Focus and Rest
        </div>
        <input ref='theInput'
               value={this.state.userInput}
               onKeyUp={this.handleKeyUp} />
      </div>
    );
  }
});
React.renderComponent(<App />, document.getElementById('example'));

在這個範例裡,render() 方法裡回傳的結構中包含了一個 <input /> 的結構描述,不過這次不同的是這個物件可以透過 this.refs.theInput 取得。然後在 clearAndFocusInput 函式裡使用 this.refs.theInput。

總結
比起使用 this.propsthis.state,_this.refs_是操作物件或傳送訊息給特定子元素最方便的方式,但是建議不要透過他們去操作你的資料。一般來說被動處理計算的資料應該使用 this.propsthis.state
Refs 的用途
* 可以定義任何 public 的 method 在你的元件類別裡(例如 reset 方法),然後透過 refs 去呼叫。
* 在你需要呼叫 DOM 的 API 時取得該元素。this.refs.myInput.getDOMNode()
* Refs 會自動記錄,如果你的子元素被破壞這個 refs 也會被破壞。不用擔心記憶體的問題,除非你做了一些瘋狂的事情,像是把整個物件都加入參考。

注意事項
* 不要在 render() 方法裡面使用 this.refs 或者當任何元件的 render() 正在運行的時候。
* 如果你想要使用 Google Closure Compiler (Javascript minify) ,請檢查不要用任何指定屬性屬性的方式使用 refs,這意味著當你設定了一個 _ref='myRefstring'_那麼你最好使用 this.refs['myRefString'] 這種方式。
* 如果你是第一次用 React 開發,通常你會傾向讓 this.refs 去幫你達到你要的功能,如果遇到這種情形,請審慎思考關鍵在哪,該如何設計階層結構,該 state 管理控制哪些資料。
通常把 state 放置在元件最高階層,控制好關於『自己』的狀態會讓程式碼變得乾淨,清晰易懂。良好的設計 state 會導致你不會一直去使用 this.refs 強迫控制元件,且會讓資料易於控制和正確。


上一篇
Reactjs Day 13 - 與瀏覽器之間的運作
下一篇
Reactjs Day 15 - 工具整合
系列文
Reactjs 30 天邊做邊學系列30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言